package hyl.ext.ws;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;

import javax.websocket.Session;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import hyl.core.MyFun;
import hyl.core.run.MyRun;
import hyl.ext.base.MySession;
import hyl.ext.base.SessionFactory;
import hyl.ext.ws.msg.WsMsgS;

/**
 * ws基础组件1 双层模型
 * 
 * 适用于 终端管理 类似于大屏数据推送<br>
 * 
 * 一个用户只能有一个终端在线,如果 有第二个终端 第一个终端会被迫下线
 * 
 * 注意: <br>
 * 
 * 1.客户终端id 必须是字符串格式 与 用户id integer编码不同<br>
 * 
 * 2.WsServe2与 WsServe3 主题名称 必须不同,否则会串扰<br>
 * 
 * @author 37798955@qq.com
 *
 */

public class WsServe2 extends WsServe {
	public static final Logger Log = LoggerFactory.getLogger(WsServe2.class);
	// 静态变量，用来记录当前在线连接数。应该把它设计成线程安全的。
	protected static AtomicInteger onlineCount = new AtomicInteger(0);
	private String basemsg = "";

	/**
	 * 房间
	 */
	protected WsRoom _room = null;

	/**
	 * (严格登录模式)<br>
	 * 
	 * 连接建立成功后 调用的方法<br>
	 * 
	 * 
	 * @param session 可选的参数。session为与某个客户端的连接会话，需要通过它来给客户端发送数据
	 */
	public void loginByToken(String token, Session session) {
		if (MyFun.isEmpty(token)) {
			close();
			sendTo(0,"令牌为空异常");
			return;
		}
		// 判断该用户登录了吗,如果登录了,才能连接
		if (SessionFactory.isLogin(token)) {
			this._token = token;
			MySession ms = SessionFactory.getSessionById(token);
			int id1 = ms.getUserId();
			loginById(String.valueOf(id1), session);
			return;
		}
		sendTo(0,String.format("该令牌已过期,ip=%s", _ip));
		close();
		return;
	}

	/**
	 * 
	 * (简单的登录方式)
	 * 
	 * id 方式登录 如果有相同的id 就把他踢下去,
	 * 
	 * 这种登录方式 比较适合大屏终端场景
	 * 
	 * @param id      身份ID主题
	 * @param session
	 */

	public void loginById(String id, Session session) {
		_clientid = id;
		_Ws会话 = session;
		// 如果会员编号为0 , 关闭当前连接 ,退出
		// 如果不为0 ,且 该编号已经存在,关闭当前连接 ,退出
		// MyHttp.get外网IP()

		_ip = WsUtil.getRemoteAddress(session);
		if (MyFun.isEmpty(_clientid) || _ip == null) {
			close();
			this.sendTo(0,"发现异常客户端0,ip=" + _ip);
			return;
		}
		if (_在线终端集.containsKey(_clientid)) {
			// 关闭原来的socket ,重新建立连接, 把原来的客户踢下去
			WsServe2 it = (WsServe2) _在线终端集.get(_clientid);
			it.close();
			it = null;
		}
		login();
		basemsg = "id:" + _clientid + ",_ip:" + _ip;
		int a = onlineCount.incrementAndGet(); // 在线数加1 ,刷新会增加1吗?
		Log.info(basemsg + "接入,累计:" + a);
	}

	void login() {
		_room = _在线房间集.get(_clientid);
		if (_room == null) {
			_room = new WsRoom(_clientid,"双");
			_在线房间集.put(_clientid, _room);
			// 发送历史消息
			List<WsMsgS> list = _room.loadMsgs();
			if (list != null) {
				for (WsMsgS wm : list) {
					sendTo(wm);
				}
			}
		} else {
			if (!WsRoom.D双层.equals ( _room._模型)) {
				sendTo(0,_clientid + "编号已被其他模型使用");
				close();
				return;
			}
		}
		_在线终端集.put(_clientid, this);
		MyFun.printJson(WsServe._在线终端集);
	}

	/**
	 * 连接后关闭调用的方法
	 */
	public void onClose() {
		if (_clientid == null)
			return;
		_在线终端集.remove(_clientid);
		int a = onlineCount.decrementAndGet(); // 在线数减1
		Log.info(basemsg + "退出,累计:" + a);
	}

	///////////////////////////
	/**
	 * @param 指令 仅限 订阅和退订指令
	 * @param 参数
	 * @return
	 */
	public void h主题事件(String 指令, String 主题) {
		if (指令.equals("订阅")) {
			_主题管理器.f订阅主题(_clientid, 主题);
		} else if (指令.equals("退订")) {
			_主题管理器.f取消订阅(_clientid, 主题);
		}

	}
	public void onMessage(byte[] 消息, Session session) {
		
	}
	/**
	 * 这个方法与上面几个方法不一样。没有用注解，是根据自己需要添加的方法。
	 * 
	 * @param message
	 * @throws IOException
	 */
	public static synchronized int getOnlineCount() {
		return onlineCount.intValue();
	}

/////////////////////发送文本/////////////////

	protected static void sendMsg(WsMsgS 消息) {
		WsServe se = _在线终端集.get(消息.接收方);
		
		if (se == null) {
			WsRoom wc = _在线房间集.get(消息.接收方);
			if (wc == null) {// 如果客户不在缓存中就立即创建一个
				wc = new WsRoom(消息.接收方);
				_在线房间集.put(消息.接收方, wc);
			}
			wc.saveMsgs(消息);
		} else {
			// 否则发送到
			se.sendTo(消息);
		}
	}

	public static String sendTo(String 接收方, String 内容) {
		WsMsgS 消息 = WsMsgS.getInstance(null);
		消息.setMsg(接收方, "消息" , 内容);
		sendMsg(消息);
		return WsUtil.成功;
	}

	/**
	 * 特别注意 主题名称必须不同<br>
	 * *
	 * 
	 * @param topic   最好用 "大屏1" 开头
	 * @param content
	 * 
	 */

	public static String sendTopic(String 主题, String content) {
		// MyRun.start用户线程(null)
		Set<String> 订阅人集 = _主题管理器.get某主题的订阅人(主题);
		if (订阅人集 == null || 订阅人集.isEmpty())
			return 主题 + "主题没有订阅人";
		WsMsgS 消息 = WsMsgS.getInstance(null);
		MyRun.start用户线程(() -> {
			for (String 订阅人 : 订阅人集) {
				消息.setMsg(订阅人, 主题, content);
				sendMsg(消息);
			}
		});
		return WsUtil.成功;
	}

/////////////////////发送字节流/////////////////
	public static String sendTo(String 接收人, byte[] 内容) {
		if (内容 == null || 内容.length == 0)
			return WsUtil.er内容不能为空;
		return sendTo(接收人, ByteBuffer.wrap(内容));
	}

	public static String sendTo(String 终端id, ByteBuffer content) {
		WsServe2 wbc = (WsServe2) _在线终端集.get(终端id);
		if (wbc == null) {
			return "客户[" + 终端id + "]离线中";
		} else {
			wbc.sendTo(content);
			return "发送完毕";
		}
	}

	public static String sendTopic(String 主题, byte[] 内容) {
		if (内容 == null)
			return WsUtil.er内容不能为空;
		return sendTopic(主题, ByteBuffer.wrap(内容));
	}

	/**
	 * 特别注意 主题名称必须 与 WsServer3中的主题不同<br>
	 * *
	 * 
	 * @param topic   最好用 "大屏1" 开头
	 * @param content 可以为空 非必填 适用于长字节数组 发送 4k以内的短字节数组不需要考虑
	 * 
	 *                如果主题涉及的客户端很多也可以考虑使用
	 */

	public static String sendTopic(String 主题, ByteBuffer content) {
		Set<String> 订阅人集 = _主题管理器.get某主题的订阅人(主题);
		if (订阅人集 == null || 订阅人集.isEmpty())
			return 主题 + "主题没有订阅人";
		MyRun.start用户线程(() -> {
			for (String 订阅人 : 订阅人集) {
				WsServe2 wc = (WsServe2) _在线终端集.get(订阅人);
				if (wc != null) {
					wc.sendTo(content);
				}
			}
		});
		return WsUtil.成功;
	}

	public static void closeAll() {
		_在线终端集.forEach((k, v) -> v.close());
		onlineCount.set(0);
	}

}